#include <opencv2/opencv.hpp>

#ifndef ALGORITHM_HPP
#define ALGORITHM_HPP

#include <string>

class Algorithm
{
private:
  virtual void run_private(cv::Mat orig, cv::Mat mask, cv::Mat &result, int windowSize = 9);
  virtual cv::Mat calculateFillFront(cv::Mat &mask);
  virtual cv::SparseMat calculateSparseFillFront(cv::Mat &fillFront);
  virtual const cv::SparseMat::Node *calculateMaxPriorityPoint(cv::Mat &fillFront, cv::Mat &confidence_padded, cv::Mat &data_paddeed, cv::Mat &dx, cv::Mat &dy, cv::Mat &nx, cv::Mat &ny, int windowSize, float alpha);
  virtual float calculateDataTerm(cv::Mat &dx, cv::Mat &dy, cv::Mat &nx, cv::Mat &ny, int i, int j, int offset, float alpha);
  virtual float calculateConfidenceTerm(cv::Mat &confidence, int i, int j, int windowSize, int offset);
  virtual void visConfidence(cv::Mat &confidence, cv::Mat &orig, cv::Mat &mask, int windowSize);
  virtual void visDataTerm(cv::Mat &dataTerm, cv::Mat &orig, cv::Mat &mask, int windowSize, float alpha);

  virtual void initMat(cv::Mat &orig, cv::Mat &mask, cv::Mat &image_padded, cv::Mat &mask_padded, cv::Mat &confidence_padded, cv::Mat &data_paddeed, cv::Mat &result, int offset);
  virtual void update_gradients(cv::Mat &image_padded, cv::Mat &mask_padded, cv::Mat &dx, cv::Mat &dy);
  virtual void update_fillfront(cv::Mat &mask_padded, cv::Mat &fillFront_padded, cv::Mat &nx, cv::Mat &ny);
  virtual void patch(cv::Mat &image_padded, cv::Mat &mask_padded, int i, int j, int windowSize, cv::Point2i best_ij);

protected:
  virtual int calculateWS(float confidence, float data, int windowSize) = 0;
  virtual float calculatePriority(float data_term, float conf_term) = 0;
  virtual void visualize(cv::Mat &image_padded, cv::Mat &mask_padded, int windowSize) = 0;
  virtual void precedure(cv::Mat &image_padded, cv::Mat &mask_padded) = 0;
  virtual void findCandidatePatch(cv::Mat &image_padded, cv::Mat &mask_padded, int i, int j, int windowSize, std::vector<std::pair<float, cv::Point2i>> &candidators) = 0;
  virtual cv::Point2i findBestPatch(cv::Mat &image_padded, cv::Mat &mask_padded, int i, int j, int windowSize, std::vector<std::pair<float, cv::Point2i>> &candidators, cv::Point2f dxdy) = 0;
  virtual void afterIteration(cv::Mat &image_padded, cv::Mat &mask_padded, cv::Mat &confidence_padded, cv::Mat &data_paddeed, int i, int j) = 0;

public:
  //virtual ~Algorithm() = 0;
  int run(cv::Mat orig, cv::Mat mask, cv::Mat &result, int windowSize = 9)
  {
    const time_t start_time = time(NULL);
    this->run_private(orig, mask, result, windowSize);
    const time_t end_time = time(NULL);
    return end_time - start_time;
  };

  cv::Point2f max_edge_vector(cv::Mat &dx, cv::Mat &dy, int i, int j, int offset)
  {
    float max_edge_power = -1, i_data = -1, j_data = -1;
    for (int k = -offset; k <= offset; ++k)
    {
      for (int l = -offset; l <= offset; ++l)
      {
        int n_i = i + k;
        int n_j = j + l;

        float edge_power = pow(dx.at<float>(n_i, n_j), 2) + pow(dy.at<float>(n_i, n_j), 2);
        if (edge_power > max_edge_power)
        {
          max_edge_power = edge_power;
          i_data = n_i;
          j_data = n_j;
        }
      }
    }
    return cv::Point2i(dx.at<float>(i_data, j_data),dy.at<float>(i_data, j_data));
  };

  double cos_vector(cv::Point2d a, cv::Point2d b)
  {
    double abs_m = abs_vector(a.x, a.y);
    double abs_n = abs_vector(b.x, b.y);
    double data_term = abs(a.x * b.x + a.y * b.y);
    if (abs_m == 0 || abs_n == 0)
      return 1;
    data_term /= abs_m;
    data_term /= abs_n;
    if (data_term > 1)
      return 1;
    if (data_term < 0)
      return 0;
    return data_term;
  };

  double abs_vector(float x, float y)
  {
    return pow(pow(x, 2.0) + pow(y, 2.0), 0.5);
  };

  cv::Mat pHashValue(cv::Mat &src);
  int calHammingDistance(cv::Mat modelHashCode, cv::Mat testHashCode);
};

#endif